use actix_web::error::ErrorInternalServerError;
use lettre::AsyncTransport;
use once_cell::sync::Lazy;
use rand::{distributions::Alphanumeric, rngs::OsRng, Rng};
use regex::Regex;
use wither::{bson::doc, Model};

use crate::{
    errors::cons_error, mdb::MDB, models::email_code::EmailCodeModel, AppError, AppResult,
    ArcConfig,
};

#[derive(Debug, Clone)]
pub struct EmailService {
    config: ArcConfig,
    mdb: MDB,
}

impl EmailService {
    pub fn new(config: ArcConfig, mdb: MDB) -> Self {
        Self { config, mdb }
    }
    pub async fn send_register_code(&self, email: String) -> AppResult<()> {
        static ISMAIL_PATTEN: Lazy<Regex> =
            Lazy::new(|| Regex::new("[0-9a-zA-Z_-]+@[0-9a-zA-Z_-](\\.[0-9a-zA-Z_-])*").unwrap());
        static CODE_PATTEN: Lazy<Regex> =
            Lazy::new(|| Regex::new("\\{\\{VALID_CODE\\}\\}").unwrap());
        if !self.config.app.enable_email_validation {
            return Err(AppError::ConsError(cons_error::MSG_EMAIL_VALID_NOT_ENABLE));
        }
        if !ISMAIL_PATTEN.is_match(&email) {
            return Err(AppError::ConsError(cons_error::MSG_EMAIL_INVALID));
        }
        if let Some(ref email_config) = self.config.email {
            let coll = EmailCodeModel::collection(&*self.mdb);
            let count = coll
                .count_documents(
                    doc! {
                        "email":&email
                    },
                    None,
                )
                .await?;
            if count >= 3 {
                return Err(AppError::ConsError(cons_error::MSG_EMAIL_VALID_TOO_MORE));
            }
            let code = String::from_utf8(
                std::iter::repeat(())
                    .map(|()| OsRng.sample(Alphanumeric))
                    .take(6)
                    .collect::<Vec<_>>(),
            )
            .unwrap_or_default();
            let message = email_config
                .build_message(email.parse().map_err(ErrorInternalServerError)?, |s| {
                    CODE_PATTEN.replace(s, &code).to_string()
                })?;
            let mailer = email_config.build_mailer::<lettre::Tokio1Executor>()?;
            mailer
                .send(message)
                .await
                .map_err(ErrorInternalServerError)?;
            let mut model = EmailCodeModel {
                email,
                code,
                ..Default::default()
            };
            model.save(&*self.mdb, None).await?;
            Ok(())
        } else {
            Err(AppError::ConsError(cons_error::MSG_EMAIL_CONFIG_NOT_SET))
        }
    }
    pub async fn valid_register_code(&self, email: String, code: String) -> AppResult<bool> {
        let res = EmailCodeModel::find_one_and_delete(
            &*self.mdb,
            doc! {
                "email":email,
                "code":code
            },
            None,
        )
        .await?;
        Ok(res.is_some())
    }
}
